/*                          UX-Framework                               
 *                                                                 
 *  iZE Co.,Ltd. Copyright 2009 All rights reserved.          
 *  This file is distributed under the terms of the GNU Lesser 
 *  General Public License, described in file LICENSE.                                     
 */
/** 
 * UXInteractionクラス.
 *
 * @author		iZE Co., Ltd.<br>
 * @version		$Id$<br>
 */
package jp.ize.ux;

// {{{ imports
import jp.ize.ux.UXFocusManager;
import jp.ize.ux.acts.Act;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.KeyboardEvent;
import flash.events.FocusEvent;
// }}}
// {{{ class UXInteraction<S>
/**
 * UXInteractionクラス<br>
 * 反応するユーザーの操作(events)、状態判定(judge)、画面反応(acts)、内部状態の更新(work)を一体化した、
 * ユーザーとの相互作用(Interaction)を抽象化したUX-Frameworkの根幹を成すクラス(の一つ)です。
 * UX-Frameworkでは、UXInteractionによってユーザーとの'相互作用'を具体的なオブジェクトと切り離して記述できます。<br>
 *
 * @author		iZE Co., Ltd.<br>
 * @version		$Id$<br>
 */
class UXInteraction<S>
{
	// {{{ properties
	// {{{ public var events : Array<String>;
	/**
	 * このインタラクションが反応するイベント名です。子コンポーネントにこの指定されたイベントのどれかが発生すると、
	 * 仕事の実行を試みます。
	 */
	public var events : Array<String>;
	// }}}
	// {{{ public var ctrl : UXController<S>;
	/**
	* このインタラクションを保持する親UXControllerインスタンスです。
	* UXControllerに追加された時に代入されます。
	*/
	public var ctrl : UXController<S>;
	// }}}
	// {{{ public var acts : Array<S->Void>;
	/**
	* このインタラクションに登録されているアクションです。
	*/
	public var acts : Array<S->Void>;
	// }}}
	// {{{ public var sequential:Bool;
	/**
	* アクションを順次実行にするかどうかを示します。
	* デフォルトは false です。
	*/
	public var sequential:Bool;
	// }}}
	// {{{ public var defaults : Array<S->Void>;
	/**
	* このインタラクションに登録されているデフォルトのアクションです。
	* デフォルトアクションを指定したい場合は、この変数に直接アクションを登録します。
	*/
	public var defaults : Array<S->Void>;
	// }}}
	// {{{ public var binds : Array<String>;
	/**
	* このインタラクション終了後にbindされたインタラクションを動作させます。
	* これはそのための bind 管理配列です。
	*/
	public var binds : Array<String>;
	// }}}
	// {{{ public var namedListeners : Hash<S->Void>;
	/**
	* このインタラクションが発生した時に呼び出されるリスナー達です。
	*/
	public var namedListeners : Hash<S->Void>;
	public var listeners : Array<S->Void>;
	// }}}
	// {{{ public var actListeners : Hash<Array<Void->Void>>;
	/**
	* アクションの中でイベントが発生した時に呼び出されるリスナー達です。
	*/
	public var actListeners : Hash<Array<Void->Void>>;
	// }}}
	// {{{ public var enabled:Bool;
	/**
	* インタラクションが有効か無効かの状態を保持します
	*/
	public var enabled:Bool;
	// }}}
	// {{{ public var name(default, null) : String
	/**
	 * このインタラクションを識別できる名前です。
	 */
	public var name(default, null) : String;
	// }}}
	// {{{ public var judge(default, default) : S->Bool;
	/**
	 * このインタラクションで実行される仕事を行ってもいいかどうか判定する関数です。
	 */
	public var judge(default, default) : S->Bool;
	// }}}
	// {{{ public var work(default, default) : S->Void
	/**
	 * このインタラクションで実行される仕事です。
	 */
	public var work(default, default) : S->Void;
	// }}}
	// }}}
	// {{{ public function new()
	/**
	 * コンストラクタです。
	 */
	public function new()
	{
		defaults = new Array();
		events = new Array();
		acts = new Array();
		namedListeners = new Hash();
		listeners = new Array();
		enabled = true;
		sequential = false;
		actListeners = new Hash();
		binds = new Array();

		this.judge = function(s) return true;
		this.work = function(s) {};

	}
	// }}}
	// {{{ public function enable() : Void
	/**
	* このインタラクションを有効にします。
	*/
	public function enable() : Void
	{
		enabled = true;
	}

	// }}}
	// {{{ public function disable() : Void
	/**
	* このインタラクションを無効にします。
	*/
	public function disable() : Void
	{
		enabled = false;
	}

	// }}}
	// {{{ public function addReactEvent( etype : String ) : Void
	/**
	 * このインタラクションを起動するトリガーとなる低レベルイベント(マウスとキーボードのイベント)を指定します。
	 * このインタラクションが作用するオブジェクトに指定されたイベントが発生すると、doWork関数が実行され、judge関数
	 * の判断を経てwork関数が実行されます。
	 */
	public function addReactEvent( etype : String ) : Void
	{
		events.push(etype);
		if ( ctrl!=null ) {
			ctrl.addUXEvent(etype);
		}
	}
	// }}}
	// {{{ public function removeReactEvent( etype : String ) : Void
	/**
	 *　このインタラクションを起動するトリガーとなる低レベルイベント(マウスとキーボードのイベント)の登録を解除します。
	 */
	public function removeReactEvent( etype : String ) : Void
	{
		events.remove(etype);
		if ( ctrl!=null ) {
			ctrl.removeUXEvent(etype);
		}
	}
	// }}}
	// {{{ public function getEvents() : Array<String>
	/**
	 * 現在の盗賊済みイベントの取得。
	 */
	public function getEvents() : Array<String>
	{
		return events;
	}
	// }}}
	// {{{ public function setActs( as:Array<S->Void> ) : Void
	/**
	 * このインタラクションにアクションを登録します。
	 */
	public function setActs( as:Array<S->Void> ) : Void
	{
		acts = as;
	}
	// }}}
	// {{{ public function setDefaultActs() : Void
	/**
	 * このインタラクションのアクションをデフォルトのアクションに戻します。
	 */
	public function setDefaultActs() : Void
	{
		acts = defaults;
	}
	// }}}
	// {{{ public function getActs() : Array<S->Void>
	/**
	 * このインタラクションに登録されている全てのアクションを取得します。
	 */
	public function getActs() : Array<S->Void>
	{
		return acts;
	}
	// }}}
	// {{{ public function clearActs() : Void
	/**
	 * このインタラクションに登録されている全てのアクションを削除します。
	 */
	public function clearActs() : Void
	{
		acts = new Array();
	}
	// }}}
	// {{{ public function addAct( act : S -> Void ) : Void
	/**
	 * このインタラクションが発生した時に発動するアクションを登録できます。登録された順番に実行されます。
	 */
	public function addAct( act : S -> Void ) : Void
	{
		acts.push(act);		
	}
	// }}}
	// {{{ public function removeAct( act : S -> Void ) : Void
	/**
	 * 登録されているアクションから actを削除します。
	 */
	public function removeAct( act : S -> Void ) : Void
	{
		acts.remove(act);
	}
	// }}}
	// {{{ public function doWork( state : S ) : Void
	/**
	 * 状態判定を行った上で画面反応や内部状態の変更を行うメソッド。通常は、この関数が親UXControllerの子UXObjectsのリ
	 * スナーに登録され、イベントがした時に呼び出されます。プログラムが直接内部状態の変更を呼び出したい時は、第二引数にAny
	 * を指定して呼び出してください。Anyの場合には登録されたactを実行せずにworkを呼び出します。
	 */
	public function doWork( state : S ) : Void
	{
		if ( !enabled ) return;
		if( judge( state ) ) {
			if ( !sequential ) {
				for( a in acts.iterator() ) {
					a( state );
				}
				doWorkEnd(state);
			} else {
				var me = this;
				var func:Void->Void = null;
				var tmpacts = acts.copy();
				func = function() {
					if ( tmpacts.length<=0 ) {
						me.removeActListener(Act.STOP,func);
						me.doWorkEnd(state);
						return;
					}
					var a = tmpacts.shift();
					a( state );
				}
				addActListener(Act.STOP, func);
				Reflect.setField(state,"subject",this);
				if ( tmpacts.length>0 ) {
					var a = tmpacts.shift();
					a( state );
				} else {
					removeActListener(Act.STOP,func);
					doWorkEnd(state);
				}
			}
		}
	}
	// }}}
	// {{{ public function doWorkEnd( state : S ) : Void
	/**
	* doWork() の終了処理を行ないます。
	*/
	public function doWorkEnd( state : S ) : Void
	{
		work( state );
		for( listener in listeners.iterator() ) {
			listener( state ); // オブザーバーの呼び出し
		}
		for ( bind in binds ) {
			ctrl.trigger(bind,Reflect.field(state,"eventTarget"));
		}
	}

	// }}}
	// {{{ public function bind( intername:String ) : Void
	/**
	 * 別のインタラクションとbindします。
	 * このインタラクション終了後に登録したbindインタラクションが動作します。
	 */
	public function bind( intername:String ) : Void
	{
		binds.push(intername);
	}
	// }}}
	// {{{ public function addFinishHandler( f : S -> Void ) : Void
	/**
	 * 名前付きのオブザーバーを登録する関数。指定されたイベントが発生すると、登録された関数は登録された順番に呼ばれます。
	 */
	public function addFinishHandler( f : S -> Void ) : Void
	{
		addListener("finish",f);
	}
	// }}}
	// {{{ public function removeFinishHandler() : Void
	/**
	 * 名前付きのオブザーバーを削除する関数。
	 */
	public function removeFinishHandler() : Void
	{
		removeListener("finish");
	}
	// }}}
	// {{{ public function addListener( name : String, f : S -> Void )
	/**
	 * 名前付きのオブザーバーを登録する関数。指定されたイベントが発生すると、登録された関数は登録された順番に呼ばれます。
	 */
	public function addListener( name : String, f : S -> Void )
	{
		namedListeners.set( name, f );
		listeners.push( f );
	}
	// }}}
	// {{{ public function removeListener( name : String )
	/**
	 * 指定された名前のイベントオブザーバーの登録を解除する関数です。
	 */
 	public function removeListener( name : String  )
	{
		var l = namedListeners.get(name);
		listeners.remove( l );
		namedListeners.remove(name);
	}
	// }}}
	// {{{ public function addActListener( name : String, f : Void -> Void )
	/**
	 * アクション用の名前付きのオブザーバーを登録する関数。
	 */
	public function addActListener( name : String, f : Void -> Void )
	{
		var funcs:Array<Void->Void> = null;
		if ( actListeners.exists( name ) ) {
			funcs = actListeners.get(name);
		} else {
			funcs = new Array<Void->Void>();
		}
		funcs.push(f);
		actListeners.set( name, funcs );
	}
	// }}}
	// {{{ public function removeActListener( name:String, f:Void->Void )
	/**
	 * アクション用の名前付きのオブザーバーを削除する関数。
	 */
	public function removeActListener( name:String, f:Void->Void )
	{
		if ( actListeners.exists( name ) ) {
			var funcs = actListeners.get( name );
			funcs.remove(f);
			actListeners.set(name,funcs);
		}
	}
	// }}}
	// {{{ public function setActSequentialMode( b:Bool ) : Void
	/**
	* アクションを同時実行ではなく順次実行にするかどうかを指定します。
	* true の場合、順次実行になります。
	*/
	public function setActSequentialMode( b:Bool ) : Void
	{
		sequential = b;
	}

	// }}}
	// {{{ public function notify( name:String ) : Void
	/**
	 * アクション・イベントを通知する関数。
	 */
	public function notify( name:String ) : Void
	{
		if ( actListeners.exists( name ) ) {
			var funcs = actListeners.get(name);
			for ( func in funcs ) {
				func();
			}
		}
	}
	// }}}
}
// }}}
